﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;

namespace OpenLS.Spreadsheet.Formats.Biff.Records
{
    internal class AutoFilter12Record : BiffRecord
    {
        private FrtRefHeader frtRefHeader = new FrtRefHeader() { rt = 0x087E, grbitFrt = FrtFlags.fFrtAlert };
        private ushort iEntry;
        private bool fHideArrow;
        private uint ft;
        private DynamicFilterType cft;
        private bool fWorksheetAutoFilter = true;
        private uint idList = 0xFFFFFFFF;
        private Guid guidSview;
        private uint cDateGroupings;
        private uint cCriteria;
        private List<AF12Criteria> rgCriteria = new List<AF12Criteria>();
        private List<AF12DateInfo> rgDateGroupings;
        private uint unused2;

        public AutoFilter12Record(FilterColumn column, ValuesFilter valuesFilter): this(                (ushort)
                (column.Column.Index - column.AutoFilter.Range.TopLeftCell.Column.Index), DynamicFilterType.Null, column.Range.Address)
        {
            cCriteria = (uint)valuesFilter.Filters.Count;
            cDateGroupings = (uint)valuesFilter.DateGroupItems.Count;
        }

        public static IEnumerable<BiffRecord> GetRecords(FilterColumn column, DynamicFilter dynamicFilter)
        {
            AutoFilter12Record result =
                new AutoFilter12Record(
                    (ushort) (column.Column.Index - column.AutoFilter.Range.TopLeftCell.Column.Index),
                    dynamicFilter.Type, column.Range.Address);
            switch (dynamicFilter.Type)
            {
                case DynamicFilterType.AboveAverage:
                    result.cCriteria ++;
                    result.rgCriteria.Add( new AF12Criteria(AFDOper.grbitSignType.GreaterThan, new CellValueDistribution(column.RangeWithoutHeader).Average));
                    result.cCriteria ++;
                    result.rgCriteria.Add( new AF12Criteria(AFDOper.grbitSignType.Undefined));
                    break;


            }
            yield return result;
            foreach (var item in result.rgCriteria)
            {
                AF12Criteria item1 = item;
                yield return new ContinueFrt12Record(item1.Write, column.Range.Address);
            }
        }

        public AutoFilter12Record()
        {
        }

        private AutoFilter12Record(ushort column, DynamicFilterType type, RangeAddress address)
        {
            this.iEntry = column;
            this.cft = type;
            this.frtRefHeader.ref8 = (Ref8) address;
        }

        public override void ReadWithContinue()
        {
            ContinueFrt12Record n = this.Next as ContinueFrt12Record;
            this.rgCriteria = new List<AF12Criteria>();
            for (int i = 0; i < cCriteria; i++)
            {
                rgCriteria.Add(n.GetItem(r => new AF12Criteria(r)));
                n = n.Next as ContinueFrt12Record;
            }
            this.rgDateGroupings = new List<AF12DateInfo>();
            for (int i = 0; i < cDateGroupings; i++)
            {
                rgDateGroupings.Add(n.GetItem(r => new AF12DateInfo(r)));
                n = n.Next as ContinueFrt12Record;
            }
        }
        protected override void ReadContent(BiffReaderContext context, int length)
        {
            this.frtRefHeader = new FrtRefHeader();
            this.frtRefHeader.Read(context.Reader);
            Contract.Assert(frtRefHeader.rt == 0x087E);
            Contract.Assert(frtRefHeader.grbitFrt == (FrtFlags) 1);
            this.iEntry = context.ReadUInt16();
            this.fHideArrow = context.ReadUInt32() != 0;
            this.ft = context.ReadUInt32();
            this.cft = (DynamicFilterType) context.ReadInt32();
            this.cCriteria = context.ReadUInt32();
            this.cDateGroupings = context.ReadUInt32();
            this.fWorksheetAutoFilter =( context.ReadUInt16() & 0x8) != 0;
            this.unused2 = context.ReadUInt32();
            this.idList = context.ReadUInt32();
            this.guidSview = new Guid(context.ReadBytes(16));
            switch (ft)
            {
                case 0:
                    break;
                case 1:
                case 2:
                case 3:
                    throw new NotImplementedException();
                default:
                    throw new NotSupportedException();
            }
        }

        internal override BiffRecordType RecordType
        {
            get { return BiffRecordType.AutoFilter12; }
        }

        internal override void WriteContent(BiffWriterWorkbookContext context)
        {
            Contract.Assert(frtRefHeader.rt == 0x087E);
            Contract.Assert(frtRefHeader.grbitFrt == (FrtFlags)1);
            this.frtRefHeader.Write(context.Writer);
            context.Write(this.iEntry);
            context.Write(fHideArrow?1:0);
            context.Write(ft);
            context.Write((int)cft);
            context.Write(cCriteria);
            context.Write(cDateGroupings);
            if (fWorksheetAutoFilter)
                context.Write((ushort)0x08);
            else
            {
                context.Write((ushort)0x00);
            }
            context.Write(this.unused2);
            context.Write(idList);
            context.Write(guidSview.ToByteArray());
            switch (ft)
            {
                case 0:
                    break;
                case 1:
                case 2:
                case 3:
                    throw new NotImplementedException();
                default:
                    throw new NotSupportedException();
            }
        }

        public Filter GetFilter()
        {
            Contract.Ensures(Contract.Result<Filter>() != null);
            return new DynamicFilter() {Type = this.cft};

        }
    }

    internal class AF12DateInfo
    {
        private ushort year;
        private ushort month;
        private ushort minute;
        private uint reserved1;
        private NodeType nodeType;
        private ushort second;
        private uint day;
        private ushort hour;
        private ushort unused1;

        public AF12DateInfo(BinaryReader reader)
        {
            Read(reader);
        }
        enum NodeType
        {
            Year=0,
            Month=1,
            Day=2,
            Hour=3,
            Minute=4,
            Second=5
        }

        public void Read(BinaryReader reader)
        {
            this.year = reader.ReadUInt16();
            this.month = reader.ReadUInt16();
            this.day = reader.ReadUInt32();
            this.hour = reader.ReadUInt16();
            this.minute = reader.ReadUInt16();
            this.second = reader.ReadUInt16();
            this.unused1 = reader.ReadUInt16();
            this.reserved1 = reader.ReadUInt32();
            this.nodeType = (NodeType)reader.ReadInt32();
        }

        public void Write(BinaryWriter writer)
        {
            writer.Write(year);
            writer.Write(this.month  );
            writer.Write(this.day  );
            writer.Write(this.hour  );
            writer.Write(this.minute  );
            writer.Write(this.second  );
            writer.Write(this.unused1  );
            writer.Write(this.reserved1  );
            writer.Write((int) this.nodeType);
        }
    }

    internal class AF12Criteria
    {
        private AFDOper doper;
        private XLUnicodeStringNoCch str;

        public AF12Criteria(BinaryReader binaryReader)
        {
            Read(binaryReader);
        }

        public AF12Criteria(string s)
        {
            doper = new AFDOper();
            doper.grbitSign = AFDOper.grbitSignType.EqualTo;
            doper.vt = AFDOper.VtValueType.AFDOperStr;
            doper.vtValue = new AFDOper.AFDOperStr(s.Length);
                str = (XLUnicodeStringNoCch) s;
        }
        public AF12Criteria(AFDOper.grbitSignType type, double value)
        {
            doper = new AFDOper();
            doper.grbitSign = type;
            doper.vt = AFDOper.VtValueType.Xnum;
            doper.vtValue = value;
        }

        public AF12Criteria(AFDOper.grbitSignType type)
        {
            doper = new AFDOper {grbitSign = type, vt = AFDOper.VtValueType.Ignored};
        }


        public void Read(BinaryReader reader)
        {
            this.doper = new AFDOper();
            doper.Read(reader);
            this.str = new XLUnicodeStringNoCch();
            if (doper.vt == AFDOper.VtValueType.AFDOperStr )
            {
                var s = (AFDOper.AFDOperStr)doper.vtValue;
                str.Read(reader, s.cch);
            }
        }

        public void Write(BinaryWriter writer)
        {
            doper.Write(writer);
            if (doper.vt == AFDOper.VtValueType.AFDOperStr)
            {
                str.Write(writer);
            }
        }
    }
}